/**
 * Para 对象
 */
export const P = {
  /**
   * 判断是否数值类型
   */
  isNumber: (number, fnName, argName) => {
    if (!Number.isFinite(number)) {
      if (fnName && argName) {
        throw new Error(
          `${fnName}: ${argName} 必须是数值类型，但获取到的值不是数值`
        );
      }
    } else {
      return true;
    }
  },

  /**
   * 判断是否整数类型
   */
  isInteger: (number, fnName, argName) => {
    if (!P.isNumber(number) || Math.trunc(number) !== number) {
      if (fnName && argName) {
        throw new Error(
          `${fnName}: ${argName} 必须是整数类型，但获取到的值不是整数`
        );
      }
    } else {
      return true;
    }
  },

  /**
   * 判断是否是合理的数组大小（大于等于 0 的整数）
   */
  isSize: (size, fnName, argName) => {
    if (!P.isInteger(size) || size <= 0) {
      if (fnName && argName) {
        throw new Error(`${fnName}: ${argName} 必须是一个大于 0 的整数`);
      }
    } else {
      return true;
    }
  },

  /**
   * 判断是否是合理的数组下标
   */
  isIndex: (array, index, fnName, argName) => {
    if (!P.isInteger(index) || index < 0 || index >= array.length) {
      if (fnName && argName) {
        throw new Error(`${fnName}: ${argName} 超出了提供的数组下标范围`);
      } else {
        return true;
      }
    }
  },

  /**
   * 判断是否数组类型
   */
  isArray: (array, fnName, argName) => {
    if (!Array.isArray(array)) {
      if (fnName && argName) {
        throw new Error(
          `${fnName}: ${argName} 必须是数组类型，但获取到的值不是数组`
        );
      }
    } else {
      return true;
    }
  },

  /**
   * 判断是否对象类型
   */
  isObject: (obj, fnName, argName) => {
    if (!obj || typeof obj !== "object" || obj.constructor !== {}.constructor) {
      if (fnName && argName) {
        throw new Error(
          `${fnName}: ${argName} 必须是对象类型，但获取到的值不是对象`
        );
      }
    } else {
      return true;
    }
  },

  /**
   * 判断是否是非空字符串
   */
  isKey: (key, fnName, argName) => {
    if (!key || typeof key !== "string") {
      if (fnName && argName) {
        throw new Error(`${fnName}: ${argName} 必须是非空字符串`);
      }
    } else {
      return true;
    }
  },

  /**
   * 判断是否函数类型
   */
  isFunction: (fn, fnName, argName) => {
    if (typeof fn !== "function") {
      if (fnName && argName) {
        throw new Error(
          `${fnName}: ${argName} 必须是函数类型，但获取到的值不是函数`
        );
      }
    } else {
      return true;
    }
  },

  /**
   * 判断是否是合理的范围取值
   */
  isRange: (start, end, step, fnName) => {
    if ((start > end && step > 0) || (start < end && step < 0)) {
      if (fnName) {
        throw new Error(`${fnName}: 参数错误，将会导致无限循环`);
      }
    } else if (step === 0) {
      throw new Error(`${fnName}: step 的值不能为 0`);
    } else {
      return true;
    }
  },

  /** **************************************************************************************************/

  /**
   * 创建一个数组并填充默认值，默认值为深拷贝方式
   * @param {number} size 数组大小
   * @param {any} defaultValue=0 默认值
   * @returns {any[]} 返回创建的数组
   */
  array: (size, defaultValue = 0) => {
    P.isSize(size, "P.array", "size");
    return P.fill(new Array(size).fill(0), defaultValue);
  },

  /**
   * @warn 测试版本，不建议直接使用
   * 生成多状态变量，可以通过切换状态改变值
   * @returns {Proxy} 返回多状态变量
   */
  auto: (defaultValue) => {
    return new Proxy(
      {
        __value: {},
        ...defaultValue,
      },
      {
        set: (target, key, value) => {
          if (key === "$") {
            let kvpairs = Object.entries(target);
            const current = target.__value[value];
            kvpairs = kvpairs.filter((val) => val[0] === "__value");
            kvpairs.push(...Object.entries(current));
            for (const kv of kvpairs) {
              target[kv[0]] = kv[1];
            }
          } else if (key.startsWith("$")) {
            const type = key.substring(1);
            target.__value[type] = value;
          }
          return true;
        },
      }
    );
  },

  /**
   * 链式调用函数，上一个函数的返回结果会作为下一个函数的参数
   * @param {any} defaultValue 默认值，作为第一个函数的参数
   * @param {Function[]} fnChain 允许传入多个函数作为参数，按顺序调用传入的函数
   * @returns {any} 返回最后一个函数的调用返回结果
   */
  call: (defaultValue, ...fnChain) => {
    fnChain.forEach((fn, index) => P.isFunction(fn, "P.call", `fnChain.<${index}>`));
    return fnChain.reduce((pre, cur, index) => {
      try {
        return cur(pre);
      } catch (err) {
        throw new Error(
          `P.call: 函数调用过程中发生错误，下标为 ${index}, 原始错误信息为 "${err}"`
        );
      }
    }, defaultValue);
  },

  /**
   * 使用时间戳和多层随机数创建对象数组的 key 值
   * @param {any[]} array 需要创建 key 值的对象数组
   * @param {string} keyName key 值对应键名称
   * @param {number} deep=2 创建随机数的次数
   * @returns {any[]} 返回创建 key 值的对象数组
   */
  createKey: (array, keyName, deep = 2) => {
    P.isArray(array, "P.createKey", "array");
    array.forEach((val) => {
      P.isObject(val, "P.createKey", "array.<item>");
      let randomValue = "";
      for (let t = 0; t < deep; t++) {
        randomValue = `${randomValue}${Math.random()}`;
      }
      if (val[keyName] === undefined) {
        val[keyName] = `${Date.now()}${randomValue}`.replaceAll(".", "");
      }
    });
    return array;
  },

  /**
   * 深拷贝数组、对象或其它值
   * @param {any} obj 需要深拷贝的值
   * @returns {any} 返回深拷贝后的值
   */
  deep: (obj) => {
    if (P.isArray(obj)) {
      obj = Array.from(obj);
      for (const index in obj) {
        obj[index] = P.deep(obj[index]);
      }
    } else if (P.isObject(obj)) {
      obj = { ...obj };
      for (const index of Object.keys(obj)) {
        obj[index] = P.deep(obj[index]);
      }
    }
    return obj;
  },

  /**
   * 删除对象中指定的键值
   * @param {object} obj 原对象
   * @param {string[]} params 需要删除的键名称
   * @returns {object} 返回删除后的对象
   */
  delete: (obj, params) => {
    P.isObject(obj, "P.delete", "obj");
    P.isArray(params, "P.delete", "params");
    for (const param of params) {
      delete obj[param];
    }
    return obj;
  },

  /**
   * 将数组分成多个子数组
   * @param {any} array 待分割的数组
   * @param {any} chunkSize 分割大小
   * @returns {any} 返回分割结果
   */
  divide: (array, chunkSize) => {
    P.isArray(array, "P.divide", "array");
    P.isSize(chunkSize, "P.divide", "chunkSize");
    const divideArrays = [];
    for (const index in array) {
      if (index % chunkSize === 0) {
        divideArrays.push([]);
      }
      P.last(divideArrays, 1).push(array[index]);
    }
    return divideArrays;
  },

  /**
   * 填充一个数组中指定范围的元素，填充元素为深拷贝方式
   * @param {any[]} array 待填充的数组
   * @param {any} obj 填充元素
   * @param {number} start=0 起始下标
   * @param {number} end=-1 结束下标
   * @returns {any[]} 返回填充结果
   */
  fill: (array, obj, start = 0, end = -1) => {
    P.isArray(array, "P.fill", "array");
    if (end < 0) {
      end = array.length + end;
    }
    P.isIndex(array, start, "P.fill", "start");
    P.isIndex(array, end, "P.fill", "end");
    for (const index in array) {
      if (index >= start && index <= end) {
        array[index] = P.deep(obj);
      }
    }
    return array;
  },

  /**
   * 作用和基础的 for 循环一样，只是改变了写法
   * @param {number} start 起始值
   * @param {number} end 结束值
   * @param {number} step 间隔值
   * @param {Function} callback 循环回调函数
   * @returns {undefined}
   */
  for: (start, end, step, callback) => {
    P.isNumber(start, "P.for", "start");
    P.isNumber(end, "P.for", "end");
    P.isNumber(step, "P.for", "step");
    P.isFunction(callback, "P.for", "callback");
    P.isRange(start, end, step, "P.for");
    if (step > 0) {
      for (let t = start; t <= end; t += step) {
        callback(t);
      }
    }
    if (step < 0) {
      for (let t = start; t >= end; t += step) {
        callback(t);
      }
    }
  },

  /**
   * 提取数组中对象类型的元素的指定键值
   * @param {any[]} array 待提取的数组
   * @param {string} key 指定键名称，使用 P.rebuild 相同的字符串形式描述，描述忽略数组本身的描述符
   * @returns {any[]} 返回提取结果
   */
  getArrayKey: (array, key) => {
    P.isArray(array, "P.getArrayKey", "array");
    P.isKey(key, "P.getArrayKey", "key");
    array.forEach((val, index) => P.isObject(val, "P.getArrayKey", `array.<${index}>`));
    return P.rebuild({ array }, (fn) => fn(`array.${key}`));
  },

  /**
   * 判断对象是否是空对象
   * @param {any} obj 待判断对象
   * @returns {any} 返回判断结果
   */
  isEmptyObject: (obj) => {
    P.isObject(obj, "p.isEmptyObject", "obj");
    if (Object.keys(obj).length === 0) {
      return true;
    } else {
      return false;
    }
  },

  /**
   * 获取数组倒数下标的元素，也可以同时赋值
   * @param {any[]} array 待获取元素的数组
   * @param {number} index 倒数下标，从 1 开始
   * @param {any} newVal 可选参数，如果有这个参数则会赋值给指定下标的元素
   * @returns {any} 返回对应的元素
   */
  last: (array, index, newVal) => {
    P.isArray(array, "P.last", "array");
    P.isSize(index, "P.last", "index");
    index = array.length - index;
    P.isIndex(array, index, "P.last", "index");
    if (newVal) {
      if (P.isObject(array[index]) && P.isObject(newVal)) {
        array[index] = {
          ...array[index],
          ...newVal,
        };
      } else {
        array[index] = newVal;
      }
    }
    return array[index];
  },

  /**
   * 作用和 Array.prototype.push 一样，不过可以限制最大长度
   * @param {any[]} array 需要使用 push 的数组
   * @param {any} value 需要 push 的值
   * @param {number} size 限制最大长度
   * @returns {any[]} 返回处理后的数组
   */
  limitPush: (array, value, size) => {
    P.isArray(array, "P.limitPush", "array");
    P.isSize(size, "P.limitPush", "size");
    if (array.length < size) {
      array.push(value);
    }
    return array;
  },

  /**
   * 作用和 Array.prototype.unshift 一样，不过可以限制最大长度
   * @param {any[]} array 需要使用 unshift 的数组
   * @param {any} value 需要 unshift 的值
   * @param {number} size 限制最大长度
   * @returns {any[]} 返回处理后的数组
   */
  limitUnshift: (array, value, size) => {
    P.isArray(array, "P.limitUnshift", "array");
    P.isSize(size, "P.limitUnshift", "size");
    if (array.length < size) {
      array.unshift(value);
    }
    return array;
  },

  /**
   * 作用和 P.protect 一样，不过是针对单个元素的数组使用的
   * @param {any[]} array 需要设置默认值的数组，只针对第一个元素设置默认值
   * @param {any} defaultValue 默认值
   * @returns {Proxy} 返回 Proxy 对象
   */
  one: (array, defaultValue) => {
    P.isArray(array, "P.one", "array");
    return P.protect(array, undefined, {
      0: defaultValue,
    });
  },

  /**
   * 使用 Proxy 的方式设置对象或数组键值的默认值，防止 undefined 和 null 等引起的错误
   * @param {object|any[]} obj 需要设置默认值的对象或数组
   * @param {any} defaultValue 默认值
   * @param {object} indicateValue={} 指定的默认值
   * @param {any[]} protectMatch=[undefined,null] 匹配规则
   * @returns {Proxy} 返回 Proxy 对象
   */
  protect: (
    obj,
    defaultValue,
    indicateValue = {},
    protectMatch = [undefined, null]
  ) => {
    P.isObject(indicateValue, "P.protect", "indicateValue");
    P.isArray(protectMatch, "P.protect", "protectMatch");
    try {
      return new Proxy(obj, {
        get: (target, key) => {
          if (protectMatch.includes(target[key])) {
            if (indicateValue.hasOwnProperty(key)) {
              return indicateValue[key];
            } else {
              return defaultValue;
            }
          } else {
            return target[key];
          }
        },
      });
    } catch {
      throw new Error("P.protect: obj 必须是对象或数组");
    }
  },

  /**
   * 获取数组中指定范围的元素，可指定获取元素间隔
   * @param {any[]} array 待获取元素的数组
   * @param {number} start=0 起始下标
   * @param {number} end=-1 结束下标
   * @param {number} step=1 获取元素间隔
   * @returns {any[]} 返回指定范围的元素
   */
  range: (array, start = 0, end = -1, step = 1) => {
    P.isArray(array, "P.range", "array");
    P.isInteger(start, "P.range", "start");
    P.isInteger(end, "P.range", "end");
    P.isInteger(step, "P.range", "step");
    if (start < 0) {
      start = array.length + start;
    }
    if (end < 0) {
      end = array.length + end;
    }
    P.isRange(start, end, step, "P.range");
    const result = [];
    if (step > 0) {
      for (let t = start; t <= end; t += step) {
        P.isIndex(array, t, "P.range", "start & end");
        result.push(array[t]);
      }
    }
    if (step < 0) {
      for (let t = start; t >= end; t += step) {
        P.isIndex(array, t, "P.range", "start & end");
        result.push(array[t]);
      }
    }
    return result;
  },

  /**
   * 将各种数据类型的数据重组成新的数据格式
   * @param {object} source 原数据
   * @param {Function} model 数据模板
   * @returns {any|any[]} 返回重组的数据
   */
  rebuild: (source, model) => {
    P.isObject(source, "P.rebuild", "source");
    P.isFunction(model, "P.rebuild", "model");
    const maxLength = Object.values(source).reduce(
      (pre, cur) => (P.isArray(cur) ? Math.max(pre, cur.length) : pre),
      0
    );
    const getSourceValue = (index) => (exp) => {
      P.isKey(exp, "P.rebuild", "model.<exp>");
      const frags = exp.split(".");
      let value = source;
      try {
        for (const frag of frags) {
          if (frag === "#") {
            value = index;
          } else if (frag === "$") {
            value = value[index];
          } else {
            value = value[frag];
          }
        }
      } catch {
        throw new Error(
          "P.rebuild: model.<exp> 找不到正确的 source，请检查传入的 model exps 是否有误，或者是 source 中的数组长度不统一"
        );
      }
      return value;
    };
    let result = null;
    if (maxLength > 0) {
      result = [];
      for (let index = 0; index < maxLength; index++) {
        result.push(model(getSourceValue(index)));
      }
    } else {
      result = model(getSourceValue(-1));
    }
    return result;
  },

  /**
   * 删除数组中指定的值
   * @param {any[]} array 待删除的数组
   * @param {any[]} values 指定值
   * @returns {any[]} 返回删除结果
   */
  remove: (array, values) => {
    P.isArray(array, "P.remove", "array");
    P.isArray(values, "P.remove", "values");
    array = array.filter((val) => !values.includes(val));
    return array;
  },

  /**
   * 将对象中的指定键名称替换为另一个名称，值不变
   * @param {object} obj 待替换的对象
   * @param {string} oldName 原键名称
   * @param {string} newName 新键名称
   * @returns {object} 返回替换结果
   */
  rename: (obj, oldName, newName) => {
    P.isObject(obj, "P.rename", "obj");
    P.isKey(oldName, "P.rename", "oldName");
    P.isKey(newName, "P.rename", "newName");
    if (obj.hasOwnProperty(oldName)) {
      obj[newName] = obj[oldName];
      delete obj[oldName];
    }
    return obj;
  },

  /**
   * 去除数组中重复的元素
   * @param {any[]} array 待去重的数组
   * @returns {any[]} 返回去重结果
   */
  set: (array) => {
    P.isArray(array, "P.set", "array");
    const set = new Set(array);
    return Array.from(set);
  },

  /**
   * 作用和 setTimeout 一样，不过采用 Promise 的方式
   * @param {number} timeout 延时时长，单位为毫秒
   * @returns {Promise} 返回 Promise 对象
   */
  sleep: async (timeout) => {
    P.isInteger(timeout, "P.sleep", "timeout");
    return new Promise((resolve) => {
      setTimeout(resolve, timeout);
    });
  },

  /**
   * 计算数组中属于数值类型的数据的统计信息
   * @param {any[]} array 待计算的数组
   * @returns {object} 返回计算结果
   */
  stat: (array) => {
    P.isArray(array, "P.stat", "array");
    array = array.filter((val) => P.isNumber(val));
    return {
      sum: array.reduce((pre, cur) => pre + cur, 0),
      max: array.reduce((pre, cur) => (pre > cur ? pre : cur), array[0]),
      min: array.reduce((pre, cur) => (pre < cur ? pre : cur), array[0]),
      avg: array.reduce((pre, cur) => pre + cur, 0) / array.length,
    };
  },

  /**
   * 测试提供值是否是真值，根据测试值的真假选择返回结果
   * @param {any} testValue 测试值
   * @param {Function} trueCallback=(val)=>val 测试值为真的回调函数
   * @param {Function} falseCallback=()=>null 测试值为假的回调函数
   * @returns {any} 返回对应的结果
   */
  test: (
    testValue,
    trueCallback = (val) => val,
    falseCallback = () => null
  ) => {
    P.isFunction(trueCallback, "P.test", "trueCallback");
    P.isFunction(falseCallback, "P.test", "falseCallback");
    if (testValue) {
      return trueCallback(testValue);
    } else {
      return falseCallback();
    }
  },
};
